/*
 * Copyright (c) 2022 Kirky
 * Licensed under the Open Software Licence, Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://opensource.org/licenses/osl-3.0.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lingeringdream.luciole.helper.camera;

import com.lingeringdream.luciole.helper.identity.IdentityHelper;
import com.lingeringdream.luciole.slice.DetailSlice;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.Surface;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.utils.Point;
import ohos.agp.window.service.Window;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.camera.CameraKit;
import ohos.media.camera.device.*;
import ohos.media.camera.params.Metadata;
import ohos.media.common.AudioProperty;
import ohos.media.common.Source;
import ohos.media.common.VideoProperty;
import ohos.media.image.ImageReceiver;
import ohos.media.image.common.ImageFormat;
import ohos.media.recorder.Recorder;

import java.util.Map;

import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PICTURE;
import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PREVIEW;

/**
 * 相机管理类
 *
 * @author Kirky
 * @version 1.0
 * @date 2022/5/1
 */
public class CameraHelper {
    private static int SCREEN_WIDTH = 1080;
    private static int SCREEN_HEIGHT = 1920;
    private final Object LOCK = new Object();
    private final EventHandler eventHandler = new EventHandler(EventRunner.current()) {
    };
    private final Context context;
    private final AbilitySlice slice;
    private final SurfaceProvider surfaceProvider;
    private final boolean isRecording, isPreview;
    private final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0xD000001, "CameraHelper");
    private Surface recorderSurface;
    private Surface previewSurface;
    private ImageReceiver imageReceiver;
    private Camera cameraDevice;
    private String cameraId = "";
    private CameraConfig.Builder cameraConfigBuilder;
    private Recorder mediaRecorder;
    private IdentityHelper identityHelper;

    public CameraHelper(Context context, AbilitySlice slice, SurfaceProvider surfaceProvider,
                        boolean isRecording, boolean isPreview) {
        this.context = context;
        this.slice = slice;
        this.surfaceProvider = surfaceProvider;
        this.isRecording = isRecording;
        this.isPreview = isPreview;
        this.identityHelper = new IdentityHelper(context);
    }

    /**
     * 初始化相机
     */
    public void init(Window window, Point point) {
        window.setTransparent(true);
        surfaceProvider.pinToZTop(false);
        if (point != null) {
            SCREEN_WIDTH = point.getPointXToInt();
            SCREEN_HEIGHT = point.getPointYToInt();
        }
        if (surfaceProvider.getSurfaceOps().isPresent()) {
            surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
        }
        imageReceiver = ImageReceiver.create(SCREEN_WIDTH, SCREEN_HEIGHT, ImageFormat.JPEG, 1);
        imageReceiver.setImageArrivalListener(imageReceiver -> {
            if (identityHelper == null) {
                identityHelper = new IdentityHelper(context);
            }
            Map<Float, String> res = identityHelper.byUrl(imageReceiver);
            Intent intent = new Intent();
//            intent.setParam("map", res.toString());
//            intent.setParam("isClassify", true);
            float max  = 0;
            for (Float f: res.keySet()) {
                max = Math.max(max, f);
            }
            intent.setParam("cid", res.get(max));
            slice.present(new DetailSlice(), intent);
        });
    }

    /**
     * 拍照
     */
    public void takeSingle() {
        if (cameraDevice == null) {
            HiLog.error(TAG,"[Take photo failed]\tcameraDevice is null!");
            return;
        }
        FrameConfig.Builder framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
        framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
        FrameConfig pictureFrameConfig = framePictureConfigBuilder.build();
        cameraDevice.triggerSingleCapture(pictureFrameConfig);
    }

    /**
     * 开始录像
     */
    public void startRecord() {
        if (cameraDevice == null) {
            HiLog.error(TAG, "[StartRecord failed]\tcameraDevice is null");
            return;
        }
        synchronized (LOCK) {
            initMediaRecorder();
            if (isRecording) {
                recorderSurface = mediaRecorder.getVideoSurface();
            }
            cameraConfigBuilder = cameraDevice.getCameraConfigBuilder();
            try {
                if (isPreview) {
                    cameraConfigBuilder.addSurface(previewSurface);
                }
                if (imageReceiver != null) {
                    cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
                }
                if (isRecording && recorderSurface != null) {
                    cameraConfigBuilder.addSurface(recorderSurface);
                }
                cameraDevice.configure(cameraConfigBuilder.build());
            } catch (IllegalStateException | IllegalArgumentException e) {
                HiLog.error(TAG, "[startRecord IllegalStateException |" +
                        " IllegalArgumentException]\t" + e.getLocalizedMessage());
            }
        }
    }

    /**
     * 停止录像
     */
    public void stopRecord() {
        synchronized (LOCK) {
            try {
                eventHandler.postTask(() -> {
                    if (mediaRecorder != null) {
                        mediaRecorder.stop();
                    }
                });
                if (cameraDevice == null || cameraDevice.getCameraConfigBuilder() == null) {
                    HiLog.error(TAG, "StopRecord cameraDevice or getCameraConfigBuilder is null");
                    return;
                }
                cameraConfigBuilder = cameraDevice.getCameraConfigBuilder();
                cameraConfigBuilder.addSurface(previewSurface);
                if (isRecording) {
                    cameraConfigBuilder.removeSurface(recorderSurface);
                }
                cameraDevice.configure(cameraConfigBuilder.build());
            } catch (IllegalStateException | IllegalArgumentException exception) {
                HiLog.error(TAG, "[stopRecord occur exception]\t" + exception.getLocalizedMessage());
            }
        }
    }

    /**
     * 释放相关资源
     */
    public void release() {
        if (cameraDevice != null) {
            cameraDevice.release();
//            identityHelper.release();
        }
    }

    /**
     * 开启闪光灯
     */
    public void openFlashLight() {
        if (cameraDevice != null) {
            FrameConfig.Builder frameConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
            frameConfigBuilder.setFlashMode(Metadata.FlashMode.FLASH_ALWAYS_OPEN);
            if (previewSurface!=null){
                frameConfigBuilder.addSurface(previewSurface);
            }
            cameraDevice.triggerLoopingCapture(frameConfigBuilder.build());
        }else {
            HiLog.error(TAG, "[OpenFlashLight Failed]\tCameraDevice is null");
        }
    }

    /**
     * 关闭闪光灯
     */
    public void closeFlashLight() {
        if (cameraDevice != null) {
            FrameConfig.Builder frameConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
            frameConfigBuilder.setFlashMode(Metadata.FlashMode.FLASH_CLOSE );
            if (previewSurface!=null){
                frameConfigBuilder.addSurface(previewSurface);
            }
            cameraDevice.triggerLoopingCapture(frameConfigBuilder.build());
        }else {
            HiLog.error(TAG, "[CloseFlashLight Failed]\tCameraDevice is null");
        }
    }

    /**
     * 开启相机
     */
    private void openCamera() {
        CameraKit cameraKit = CameraKit.getInstance(context);
        String[] cameraList = cameraKit.getCameraIds();
        if (cameraList.length < 1) {
            HiLog.error(TAG, "[openCamera failed]\tThere is no camera");
            return;
        } else if (cameraList.length == 1) {
            cameraId = cameraList[0];
        } else {
            for (String logicalCameraId : cameraList) {
                if (cameraKit.getCameraInfo(logicalCameraId).getFacingType() == CameraInfo.FacingType.CAMERA_FACING_BACK) {
                    cameraId = logicalCameraId;
                }
            }
            HiLog.info(TAG, "There are multiple cameras here. Now start the rear camera……");
        }

        if (!cameraId.isEmpty()) {
            CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
            cameraKit.createCamera(cameraId, cameraStateCallback, eventHandler);
            HiLog.info(TAG, "[openCamera success]");
        }
    }

    private void initMediaRecorder() {
        mediaRecorder = new Recorder();
        VideoProperty.Builder videoPropertyBuilder = new VideoProperty.Builder();
        videoPropertyBuilder.setRecorderBitRate(10000000);
        videoPropertyBuilder.setRecorderDegrees(90);
        videoPropertyBuilder.setRecorderFps(30);
        videoPropertyBuilder.setRecorderHeight(Math.min(1440, 720));
        videoPropertyBuilder.setRecorderWidth(Math.max(1440, 720));
        videoPropertyBuilder.setRecorderVideoEncoder(Recorder.VideoEncoder.H264);
        videoPropertyBuilder.setRecorderRate(30);

        Source source = new Source();
        source.setRecorderAudioSource(Recorder.AudioSource.MIC);
        source.setRecorderVideoSource(Recorder.VideoSource.SURFACE);
        mediaRecorder.setSource(source);
        mediaRecorder.setOutputFormat(Recorder.OutputFormat.MPEG_4);

//        File file = new File(fileDir, "VID_" + System.currentTimeMillis() + ".mp4");
//        videoPath = file.getPath();
//        StorageProperty.Builder storagePropertyBuilder = new StorageProperty.Builder();
//        storagePropertyBuilder.setRecorderFile(file);
//        mediaRecorder.setStorageProperty(storagePropertyBuilder.build());

        AudioProperty.Builder audioPropertyBuilder = new AudioProperty.Builder();
        audioPropertyBuilder.setRecorderAudioEncoder(Recorder.AudioEncoder.AAC);
        mediaRecorder.setAudioProperty(audioPropertyBuilder.build());
        mediaRecorder.setVideoProperty(videoPropertyBuilder.build());
        mediaRecorder.prepare();
    }

    private class CameraStateCallbackImpl extends CameraStateCallback {
        CameraStateCallbackImpl() {
        }

        @Override
        public void onCreated(Camera camera) {
            if (surfaceProvider.getSurfaceOps().isPresent()) {
                previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();
            }
            if (previewSurface == null) {
                HiLog.error(TAG, "[Create camera filed]\tpreview surface is null");
                return;
            }
            cameraConfigBuilder = camera.getCameraConfigBuilder();
            cameraConfigBuilder.addSurface(previewSurface);
            cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
            camera.configure(cameraConfigBuilder.build());
            cameraDevice = camera;
        }

        @Override
        public void onConfigured(Camera camera) {
            FrameConfig.Builder frameConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
            frameConfigBuilder.addSurface(previewSurface);
            if (isRecording && recorderSurface != null) {
                frameConfigBuilder.addSurface(recorderSurface);
            }
            camera.triggerLoopingCapture(frameConfigBuilder.build());

            if (isRecording) {
                eventHandler.postTask(() -> mediaRecorder.start());
            }
        }
    }

    private class SurfaceCallBack implements SurfaceOps.Callback {
        @Override
        public void surfaceCreated(SurfaceOps callbackSurfaceOps) {
            if (callbackSurfaceOps != null) {
                callbackSurfaceOps.setFixedSize(SCREEN_HEIGHT, SCREEN_WIDTH);
            }
            eventHandler.postTask(CameraHelper.this::openCamera, 200);
        }

        @Override
        public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {
        }
    }
}
